// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package base

import (
	"fmt"
	"io"
	"strings"
	"time"
)

var Timer Timings

// 计时收集标记阶段的执行时间
// 通过一系列启动/停止调用添加。
// 事件可通过AddEvent与每个阶段相关联。
type Timings struct {
	list   []timestamp
	events map[int][]*event // 懒散地分配
}

type timestamp struct {
	time  time.Time
	label string
	start bool
}

type event struct {
	size int64  // 已处理数据的计数或数量（分配、数据大小、行、函数等）
	unit string // 大小度量单位（计数、MB、行、函数等）
}

func (t *Timings) append(labels []string, start bool) {
	t.list = append(t.list, timestamp{time.Now(), strings.Join(labels, ":"), start})
}

// 开始标记新阶段的开始，并隐式停止前一阶段。
// 阶段名称是以冒号分隔的标签连接。
func (t *Timings) Start(labels ...string) {
	t.append(labels, true)
}

// 停止标记阶段的结束，并隐式启动新阶段。
// 标签将添加到结束阶段的标签中。
func (t *Timings) Stop(labels ...string) {
	t.append(labels, false)
}

// AddEvent关联事件，即计数或数据量，
// 最近开始或停止的阶段；还是第一次
// 阶段，如果尚未调用启动或停止。该单位指定
// 测量单位（例如MB、行数、函数数等）。
func (t *Timings) AddEvent(size int64, unit string) {
	m := t.events
	if m == nil {
		m = make(map[int][]*event)
		t.events = m
	}
	i := len(t.list)
	if i > 0 {
		i--
	}
	m[i] = append(m[i], &event{size, unit})
}

// Write将相位时间打印到w。
// 前缀打印在每行的开头。
func (t *Timings) Write(w io.Writer, prefix string) {
	if len(t.list) > 0 {
		var lines lines

		// 具有共享非空标签前缀的阶段组
		var group struct {
			label string        // 标签前缀
			tot   time.Duration // 累积相位时间
			size  int           // 组中收集的阶段数
		}

		// 停止/启动时间戳之间的累计时间
		var unaccounted time.Duration

		// 进程开始/停止时间戳
		pt := &t.list[0] // 前一时间戳
		tot := t.list[len(t.list)-1].time.Sub(pt.time)
		for i := 1; i < len(t.list); i++ {
			qt := &t.list[i] // 当前时间戳
			dt := qt.time.Sub(pt.time)

			var label string
			var events []*event
			if pt.start {
				// 上一阶段已开始
				label = pt.label
				events = t.events[i-1]
				if qt.start {
					// 开始隐式结束前一阶段；无事可做
				} else {
					// 停止前一阶段；附加停止标签（如有）
					if qt.label != "" {
						label += ":" + qt.label
					}
					// 与停止替换先前事件关联的事件
					if e := t.events[i]; e != nil {
						events = e
					}
				}
			} else {
				// 前一阶段已停止
				if qt.start {
					// 在停止和启动阶段之间；未知时间
					unaccounted += dt
				} else {
					// 前一个停止隐式启动当前阶段
					label = qt.label
					events = t.events[i]
				}
			}
			if label != "" {
				// 将阶段添加到现有组，或启动新组
				l := commonPrefix(group.label, label)
				if group.size == 1 && l != "" || group.size > 1 && l == group.label {
					// 添加到现有组
					group.label = l
					group.tot += dt
					group.size++
				} else {
					// 组建一个新的团队
					if group.size > 1 {
						lines.add(prefix+group.label+"subtotal", 1, group.tot, tot, nil)
					}
					group.label = label
					group.tot = dt
					group.size = 1
				}

				// 写入阶段
				lines.add(prefix+label, 1, dt, tot, events)
			}

			pt = qt
		}

		if group.size > 1 {
			lines.add(prefix+group.label+"subtotal", 1, group.tot, tot, nil)
		}

		if unaccounted != 0 {
			lines.add(prefix+"unaccounted", 1, unaccounted, tot, nil)
		}

		lines.add(prefix+"total", 1, tot, tot, nil)

		lines.write(w)
	}
}

func commonPrefix(a, b string) string {
	i := 0
	for i < len(a) && i < len(b) && a[i] == b[i] {
		i++
	}
	return a[:i]
}

type lines [][]string

func (lines *lines) add(label string, n int, dt, tot time.Duration, events []*event) {
	var line []string
	add := func(format string, args ...interface{}) {
		line = append(line, fmt.Sprintf(format, args...))
	}

	add("%s", label)
	add("    %d", n)
	add("    %d ns/op", dt)
	add("    %.2f %%", float64(dt)/float64(tot)*100)

	for _, e := range events {
		add("    %d", e.size)
		add(" %s", e.unit)
		add("    %d", int64(float64(e.size)/dt.Seconds()+0.5))
		add(" %s/s", e.unit)
	}

	*lines = append(*lines, line)
}

func (lines lines) write(w io.Writer) {
	// 确定列宽和内容
	var widths []int
	var number []bool
	for _, line := range lines {
		for i, col := range line {
			if i < len(widths) {
				if len(col) > widths[i] {
					widths[i] = len(col)
				}
			} else {
				widths = append(widths, len(col))
				number = append(number, isnumber(col)) // 第一行确定列内容
			}
		}
	}

	// 使列宽为align的倍数，以获得更稳定的输出
	const align = 1 // 设置为大于1的值以启用
	if align > 1 {
		for i, w := range widths {
			w += align - 1
			widths[i] = w - w%align
		}
	}

	// 考虑列宽和内容的打印行
	for _, line := range lines {
		for i, col := range line {
			format := "%-*s"
			if number[i] {
				format = "%*s" // 数字是右对齐的
			}
			fmt.Fprintf(w, format, widths[i], col)
		}
		fmt.Fprintln(w)
	}
}

func isnumber(s string) bool {
	for _, ch := range s {
		if ch <= ' ' {
			continue // 忽略前导空格
		}
		return '0' <= ch && ch <= '9' || ch == '.' || ch == '-' || ch == '+'
	}
	return false
}
